#pragma once

#include "onlineManage.hpp"
#include "dataManage.hpp"
#include "roomManage.hpp"
#include "log.hpp"
#include "util.hpp"
#include <list>
#include <mutex>
#include <thread>
#include <condition_variable>
#include <string>

/*-----------------------------------mathch模块------------------------------------
作用：提供一个匹配队列*/
template<class T>
class match
{
public:
    void push(T &data)// 向匹配队列当中插入用户
    {
        std::unique_lock<std::mutex> lock(_mutex);
        _match_queue.push_back(data);
        _cond.notify_all();// 队列当中有数据了，就通知一下
    }

    bool pop(T &data)// 从匹配队列当中头删一个用户
    {
        std::unique_lock<std::mutex> lock(_mutex);
        if(_match_queue.empty())// 如果为空直接退出
            return false;
        data = _match_queue.front();// 输入输出型参数
        _match_queue.pop_front();
        return true;
    }

    void remove(T &data)// 从匹配队列当中指定删除一个用户
    {
        std::unique_lock<std::mutex> lock(_mutex);
        if(!_match_queue.empty())// 不为空就删除
        {
            _match_queue.remove(data);
        }
    }
    
    size_t size() {return _match_queue.size();}// 返回用户数量
    
    bool empty() {return _match_queue.empty();}// 判断是否为空
   
    void wait()// 进入条件变量等待
    {
        std::unique_lock<std::mutex> lock(_mutex);
        _cond.wait(lock);
    }
protected:
    std::list<T> _match_queue;// 用链表模拟队列(我们需要指定删除)
    std::mutex _mutex;// 保证线程安全
    std::condition_variable _cond;// 条件变量(匹配队列有2个玩家以及以上的时候才进行游戏)
};
/*-------------------------------------------------------------------------------------*/

/*-------------------------match_manager-------------------------------------
作用：管理匹配队列，做一个生产消费模型*/
class match_manager
{
public:
    match_manager(data_manager *dm,online_manager *om,room_manager *rm)
        :_dm(dm),_om(om),_rm(rm),
        _novice_thread(&match_manager::handle_novice_thraed,this),
        _expert_thread(&match_manager::handle_expert_thraed,this),
        _god_thread(&match_manager::handle_god_thraed,this)
    {}

    void add(uint64_t uid)// 添加一个用户到匹配队列
    {
        //1. 判断该用户是否处于游戏大厅
        bool ret = _om->is_in_hall(uid);
        if(ret == false)// 只有处于游戏大厅的玩家才有资格进入匹配队列
        {
            ERROR_LOG("添加用户id%d到匹配队列失败！原因:该用户不在游戏大厅当中!",uid);
            return;
        }
        //2. 获得该玩家的天梯分数
        Json::Value info;
        ret = _dm->user_info_by_id(uid,info);// 将用户信息放到Json::Value对象当中
        if(ret == false)
        {
            ERROR_LOG("获取用户id%d的信息失败！原因：数据管理模块出错！",uid);
            return;
        }
        //3. 根据天梯分数放入不同的匹配队列
        uint64_t score = info["score"].asInt64();
        if(score <= 1000)
        {
            _novice_queue.push(uid);
        }
        else if(score > 1000 && score <= 3000)
        {
            _expert_queue.push(uid);
        }
        else
        {
            _god_queue.push(uid);
        }
    }

    void del(uint64_t uid)// 从匹配队列当中删除一个用户
    {
        //1. 判断这个用户是否在游戏大厅
        bool ret = _om->is_in_hall(uid);
        if(ret == false)
        {
            ERROR_LOG("从匹配队列当中删除用户失败！原因:用户id%d不在游戏大厅当中!",uid);
            return;
        }
        //2. 获取这个玩家的信息
        Json::Value info;
        ret = _dm->user_info_by_id(uid,info);
        if(ret == false)
        {
            ERROR_LOG("从匹配队列当中删除用户失败！原因:获取用户id%d的信息失败!",uid);
            return;
        }
        //3. 根据天梯分数的不同从不同的匹配队列当中删除
        uint64_t score = info["score"].asInt64();
        if(score <= 1000)
        {
            _novice_queue.remove(uid);
        }
        else if(score > 1000 && score <= 3000)
        {
            _expert_queue.remove(uid);
        }
        else 
        {
            _god_queue.remove(uid);
        }
    }

    //3. 三个线程处理不同的匹配队列
    void handle_novice_thraed() {handle_thread(_novice_queue);}

    void handle_expert_thraed() {handle_thread(_expert_queue);}

    void handle_god_thraed() {handle_thread(_god_queue);}

    void handle_thread(match<uint64_t>& mq)
    {
        while(true)
        {
            //1. 判断队列当中是否有两名及两名以上的玩家
            while(mq.size() < 2)
            {
                // 条件变量下阻塞
                mq.wait();
            }
            //2. 取出用户
            uint64_t uid1,uid2;
            bool ret = mq.pop(uid1);
            if(ret == false) continue;// 任意操作失败不往后执行
            ret = mq.pop(uid2);
            if(ret == false)
            {
                add(uid1);// 用户1取出来了，但是用户2取出失败，所以将用户1添加回去
                continue;// 不往后执行
            }
            //  1.判断取出的第一个用户是否在线，如果不在线，continue
            wsserver_t::connection_ptr conn1 = _om->get_hall_connect(uid1);// 获取一下处于游戏大厅的连接
            if(conn1.get() == nullptr) // 第一个用户不在线，第二个用户就还在匹配队列当中
            {
                add(uid2);
                continue;
            }
            //  2.判断去除的第二个用户是否在线，如果不在线，就要把第一个玩家添加到匹配队列当中
            wsserver_t::connection_ptr conn2 = _om->get_hall_connect(uid2);
            if(conn2.get() == nullptr) // 第二个玩家不在线，但是第一个玩家在线，扔回匹配队列当中重新匹配
            {
                add(uid1);
                continue;
            }
            //3. 两名玩家成功取出，创建一个房间
            //  1.如果创建房间失败，就要把两个玩家都重新扔到匹配队列当中去
            ret = _rm->create_room(uid1,uid2);
            if(ret == false)
            {
                ERROR_LOG("匹配失败！原因：房间创建不成功！");
                add(uid1);
                add(uid2);
                continue;
            }
            Json::Value resp;
            resp["optype"] = "match_success";
            resp["result"] = true;
            std::string body;
            ret = json_util::serialize(resp,body);// 序列化
            conn1->send(body);
            conn2->send(body);
        }
    }
protected:
    match<uint64_t> _novice_queue;// 新手队列
    match<uint64_t> _expert_queue;// 高手队列
    match<uint64_t> _god_queue;// 大神队列
    // 需要三个线程来处理不同的队列
    std::thread _novice_thread;
    std::thread _expert_thread;
    std::thread _god_thread;
    data_manager *_dm;// 数据管理模块句柄，获取天梯分数
    online_manager *_om;// 在线用户管理模块句柄
    room_manager *_rm;// 游戏房间管理模块句柄，方便后续分配游戏房间
};
/*------------------------------------------------------------------------------------------*/
